package bibliotheque;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

import portes.Porte;
import portes.PorteImpl;

public class AlgoHorlogeLogique extends AlgoAbstract {

	int nbPlaces;

	/**
	 * variables de l'algorithme Ricart et Agrawala
	 */
	// estampille local init, H = 0
	int H;
	// estampille de demande de section critique, init HSC = 0
	int HSC;
	// indique si la porte est en cours de demande d'entrée, init R = faux
	boolean R;
	// ensemble des demandeurs en attente, init X ={};
	List<Integer> X;
	// nombre de réponses attendus
	int Nrel;
	// voisinage de la porte courante
	List<Integer> V;

	// la class Message permet de différencier le message REQ, REL
	private static class Message implements Serializable {
		// type de message REL, REQ et SORTIE
		private TypMessage type;
		// valeur de l'estampille
		int value;

		public Message(TypMessage ptype, int pvalue) {
			this.type = ptype;
			this.value = pvalue;
		}

		// si type est REL et SORTIE n'a pas de beson la valeur de
		// l'estampille
		public Message(TypMessage ptype) {
			this.type = ptype;
			this.value = -1;
		}
	}

	public AlgoHorlogeLogique(Porte pPorte) {
		this.porte = pPorte;
		this.id = ((PorteImpl) pPorte).getId();
		this.nbPlaces = pPorte.nombreplacesinitial;

		// l'initialisation des variable de l'algo
		this.H = 0;
		this.HSC = 0;
		this.R = false;
		this.X = new ArrayList<Integer>();
		this.Nrel = 0;
		this.V = new ArrayList<Integer>();
		for (int i = 0; i < pPorte.nombreportes; i++) {
			if (this.id != i)
				this.V.add(i);
		}
	}

	@Override
	/**
	 * Recevoir et traiter le message transmis par la Porte liée à cette instance d'AlgoHorlogeLogique.
	 * @param from id de la Porte qui envoie le message.
	 * @param to id de la Porte qui reçoit le message (normalement la Porte liée à cette instance d'AlgoHorlogeLogique !).
	 * @param msg le message reçu, ici le type de message peux-être REL ou bien REQ 
	 * @throws RemoteException 
	 */
	public synchronized void receiveMessage(int from, int to, Serializable msg)
			throws RemoteException {
		// on cast le "msg" au type Message pour pouvoir vérifier le type de
		// "msg"
		Message message = (Message) msg;
		String s = "[Porte " + this.id + "] réception de message | from : "
				+ from + " | to : " + to + " | Type : " + message.type;
		// la valeur n'est intéressante que dans le cas d'un message REQ
		if (message.type == TypMessage.REQ) {
			s += " | Valeur : " + message.value;
		}
		((PorteImpl) porte).log(s);

		switch (message.type) {

		// accepte de demande de sortie
		case SORTIE:
			if (this.nbPlaces < this.porte.nombreplacesinitial)
				this.nbPlaces++;
			break;
		// accepte de message de permission
		case REL:
			this.Nrel--;
			this.notify();
			break;

		// accepte de demande d'entrée en section critique
		case REQ:
			this.H = (this.HSC > message.value) ? this.HSC : message.value;
			this.H++;
			// si la porte a déja fait une demande de entrée et son estampille
			// local plus petite que demandeur de sc
			if (this.R
					&& ((this.H < message.value) || ((this.H == message.value) && to < from))) {
				// on ajoute le demandeur dans l'ensemble des demandeurs en
				// attente
				this.X.add(from);
			} else {
				if (this.nbPlaces > 0)
					this.nbPlaces--;
				// sinon on envoie le message de permission (REL)
				sendMessage(to, from, new Message(TypMessage.REL));
			}
			break;
		default:
			break;
		}
	}

	@Override
	/**
	 * Envoyer un message à une autre Porte. Le message est transmis à  la méthode de même
	 * nom de la Porte liée à cette instance d'Algo.
	 * 
	 * @param from id de la Porte qui envoie le message (la Porte liée à cette instance d'AlgoHorlogeLogique).
	 * @param to id de la Porte à qui envoyer le message.
	 * @param msg le message envoyé, ici le type de message peux-être REL ou bien REQ
	 * @throws RemoteException
	 */
	public synchronized void sendMessage(int from, int to, Serializable msg)
			throws RemoteException {
		this.porte.sendMessage(from, to, msg);
		Message message = (Message) msg;
		String s = "[Porte " + this.id + "] émission de message | from : "
				+ from + " | to : " + to + " | Type : " + message.type;
		// la valeur n'est intéressante que dans le cas d'un message REQ
		if (message.type == TypMessage.REQ) {
			s += " | Valeur : " + message.value;
		}
		((PorteImpl) porte).log(s);
	}

	@Override
	/**
	 * Recevoir et traiter une demande d'entrée d'une voiture transmise par la Porte liée à cette instance d'AlgoHorlogeLogique.
	 */
	public synchronized void demandeEntree() throws RemoteException,
			InterruptedException {
		((PorteImpl) porte).log("[Porte " + this.id
				+ "] Demande d'ENTRÉE de voiture");
		this.HSC = this.H + 1;
		// s'il y a plus de place on printe le message refusé
		if (this.nbPlaces == 0) {
			((PorteImpl) porte).log("[Porte " + this.id
					+ "] entrée de voiture REFUSEE, plus de place");
		} else {
			this.R = true;
			this.Nrel = this.V.size();
			// l'envoie le demande d'entrée en section critique à tous voisinage
			// de la
			// porte courante
			for (Integer element : this.V) {
				sendMessage(this.id, element, new Message(TypMessage.REQ,
						this.HSC));
			}
			// attende de message REL de tous voisinage de la porte courante
			while (this.Nrel != 0) {
				this.wait();
			}
			// s'il y a une place disponible on printe le message de l'accorde
			if (this.nbPlaces > 0) {
				this.nbPlaces--;
				((PorteImpl) porte).log("[Porte " + this.id
						+ "] entrée de voiture ACCORDEE, reste "
						+ this.nbPlaces + " places");
			} else {
				((PorteImpl) porte).log("[Porte " + this.id
						+ "] entrée de voiture REFUSEE, plus de place");
			}
			// l'envoie de REL à ceux qui sont dans l'ensemble des demandeur en
			// attente
			// et décrémentation de nombre de place mais en reservant une place
			// pour la demande actuelle
			for (Integer element : this.X) {
				if (this.nbPlaces > 0) {
						this.nbPlaces--;
					((PorteImpl) porte).log("[Porte " + this.id
							+ "] entrée de voitures en attentes, reste " + this.nbPlaces
							+ " places");
				}
				sendMessage(this.id, element, new Message(TypMessage.REL));
			}
			this.X.clear();
		}
	}

	@Override
	/**
	 * Recevoir et traiter une demande de sortie d'une voiture transmise par la Porte liée à  cette instance d'AlgoHorlogeLogique.
	 * 
	 */
	public synchronized void demandeSortie() throws RemoteException,
			InterruptedException {
		((PorteImpl) porte).log("[Porte " + this.id
				+ "] Demande de SORTIE de voiture");
		// on incrémente le nombre de place et on envoie la demande de sortie à
		// tous voisinage de la porte courante
		if (this.nbPlaces < this.porte.nombreplacesinitial)
			this.nbPlaces++;
		for (Integer element : this.V) {
			sendMessage(this.id, element, new Message(TypMessage.SORTIE));
		}
		((PorteImpl) porte).log("[Porte " + this.id
				+ "] sortie de voiture ACCORDEE, reste " + this.nbPlaces
				+ " places");
	}

	@Override
	public void init() throws RemoteException{}

}
